/**
 * @file .
 * @author wangxiaowei <wangxiaowei0303@163.com>
 * @date 2015-05-28 16:05
 * 负责加载算法库.
 */

#include <dlfcn.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/limits.h>

#ifdef ENABLE_DEBUG
#include <stdio.h>
#endif
#include <string.h>

#include "DirTraverse.h"
#include "parseCmdLine.h"

/*
 * 从文件中读取算法库的路径,然后加载进来.
 * @param file 存放算法库路径的文件名,参见 ReadMe.txt/--file 选项文档.
 */
static void
loadAlgorithm(const char *file)
{
    /*
     * TODO 当前未实现,因为考虑到如果文件名中包含空格,特殊字符怎么处理.
     */
}

#ifdef ENABLE_DEBUG
#define DLOPEN_FLAG RTLD_NOW
#else
#define DLOPEN_FLAG RTLD_LAZY
#endif

/*
 * 加载算法库,即加载文件名为 algo 的算法库,总会追随符号链接. 
 * 若 algo 只是一个文件名(即不存在'/'字符),则 load() 会首先从当前目录开始搜索,
 * 若未查找到,则在环境变量指定的路径列表中查找.
 *
 * dlopen(filename),若 filename 只是一个文件名,则 dlopen() 并不会从当前路径开始
 * 搜素,而是直接在环境变量配置下的路径列表中搜索.
 */
static void
load(const char *algo)
{
#ifdef ENABLE_DEBUG
    printf("DEBUG;load(%s);\n",algo);
#endif
   
    void *ret = 0;
    if (strchr(algo,'/') == 0) { /* algo 只是一个文件名 */
        size_t algosize = strlen(algo);
        if (algosize > NAME_MAX)
            algosize = NAME_MAX;
        char pathbuf[2 + NAME_MAX +1] = "./";
        memcpy(&pathbuf[2],algo,algosize);
        pathbuf[2 + algosize] = '\0';

        ret = dlopen(pathbuf,DLOPEN_FLAG); /* 在当前目录下查找 */
        if (ret == 0) /* 在指定路径列表中查找 */
            ret = dlopen(pathbuf,DLOPEN_FLAG);
    } else {
        ret = dlopen(algo,DLOPEN_FLAG);
    }

    /*
     * 当前算法库加载进来之后,直至进程终止才会被卸载.所以就木有必要保存返回
     * 指针.
     */
#ifdef ENABLE_DEBUG
    if (ret == 0)
        printf("DEBUG;load(%s): failure;%s\n",algo,dlerror());
    else 
        printf("DEBUG;load(%s): success;%p\n",algo,ret);
#endif
    return ;
}

/*
 * 调用该函数处理在遍历过程中遇到的符号链接文件.
 * 具体处理方式参考 ReadMe.txt.
 */
static void
handleSymLink(const char *symlink,bool followSymLink)
{
#ifdef ENABLE_DEBUG
    printf("DEBUG;handleSymLink(%s,%s)\n",symlink,
        followSymLink ? "follow" : "nofollow");
#endif
    if (!followSymLink)
        return ;
    struct stat fileStat;
    int ret;
    ret = stat(symlink,&fileStat);
#ifdef ENABLE_DEBUG
    if (ret != 0) {
        fprintf(stderr,"ERROR;stat(%s): %m\n",symlink);
        return ;
    }
#else
    if (ret != 0) 
        return ;
#endif /* ENABLE_DEBUG */
    if (S_ISREG(fileStat.st_mode)) 
        load(symlink);
    return ;
}

/*
 * 仿函数;用在 DirTraverse 类中,负责加载遍历中的每一个文件.
 * 属性;
 *   followSymLink;若为真,则表明追随符号链接,此时对遍历中遇到的符号链接文件的处理
 *                 参考 ReadMe.txt;默认为真.
 */
struct LoadFactor {
    inline 
    LoadFactor(bool follow = true);
    
    void
    operator()(const struct dirent *entry) const;

    bool follow;
};

inline 
LoadFactor::LoadFactor(bool f)
{
    this->follow = f;
}

void
LoadFactor::operator()(const struct dirent *entry) const
{
    if (entry->d_type == DT_LNK) 
        handleSymLink(entry->d_name,this->follow);
    else if (entry->d_type == DT_REG)
        load(entry->d_name);
    return ;
}


/*
 * 遍历目录 dir,确定候选算法库集合,然后一一加载.
 */
static void
load(const char *dir,bool r,bool f)
{
#ifdef ENABLE_DEBUG
    printf("DEBUG;loaddir(%s,%s,%s)\n",
        dir,
        r ? "recursive" : "norecursive",
        f ? "follow" : "nofollow");
#endif
    DirTraverse dirTraverse;
    dirTraverse.setRecursive(r);
    LoadFactor factor(f);
    dirTraverse.traverse(dir,factor);
    return ;
}

/*
 * 从一堆路径中,加载算法库.
 * @param pathb,pathe [pathb,pathe) 确定了字符串数组.
 */
static void
loadAlgorithm(char **pathb,char **pathe,
    bool recursive,bool followSymLink)
{
    int ret = 0;
    struct stat fileStat;    
    for (; pathb != pathe; ++pathb) {
        ret = lstat(*pathb,&fileStat);
#ifdef ENABLE_DEBUG
        if (ret != 0) {
            fprintf(stderr,"ERROR;lstat(%s): %m\n",*pathb);
            continue;
        }
#else
        if (ret != 0) 
            continue;
#endif /* ENABLE_DEBUG */
        if (S_ISREG(fileStat.st_mode))
            load(*pathb);
        else if (S_ISLNK(fileStat.st_mode)) 
            handleSymLink(*pathb,followSymLink);
        else if (S_ISDIR(fileStat.st_mode)) 
            load(*pathb,recursive,followSymLink);
    }
}

void
loadAlgorithm(int argc,char **argv)
{
    /*
     * recursive; 若为真,则表明指定了 -r 选项.
     * followSymlink; 若为真,则表明指定了 -f 选项.
     * havefileOption; 若为真,则表明指定了 --file 选项.
     * fileOptionVal; 当 havefileOption 为真时,该选项指向着 --file 参数的值.
     */
    bool recursive = false;
    bool followSymlink = true;
    bool havefileOption = false;
    const char *fileOptionVal = 0;

    int ret;
    struct option options[] = {
        {"file",required_argument,0,'a'},
        {0,0,0,0}
    };
    optind = 0;
    while ((ret = parseCmdLine(argc,argv,":rRfF",options,0)) != -1) {
        switch (ret) {
        case 'a':
            havefileOption = true;
            fileOptionVal = optarg;
            break;
        case 'r':
            recursive = true;
            break;
        case 'f':
            followSymlink = true;
            break;
        case 'R':
            recursive = false;
            break;
        case 'F':
            followSymlink = false;
            break;
        default:
            break;
        }
    }/* 此时 optind 指向着 $PATH(参见 ReadMe.txt) 中第一个目录或文件名 */
    
    if (havefileOption) 
        loadAlgorithm(fileOptionVal);
    else
        loadAlgorithm(argv + optind,argv + argc,
            recursive,followSymlink);
    return ;
}


